#include "Interpreter.hpp"
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

static map<string, BuiltinFunc> *builtins;

void addBuiltin(BuiltinFunc func, const char *name)
{
    if(!builtins) builtins = new map<string, BuiltinFunc>();
	(*builtins)[name] = func;
}

bool isBuiltin(string cmd)
{
    assert(builtins != NULL);
	return builtins->find(cmd) != builtins->end();
}

bool builtinTakesAssignments(string cmd)
{
	// TODO
	return isBuiltin(cmd);
}

bool builtinDisablesFileExpansion(string cmd)
{
	if(cmd=="lcd")
		return true;
	else if(cmd=="put")
		return true;
	
	return false;
}

BuiltinFunc getBuiltin(string cmd)
{
    assert(builtins != NULL);
	return (*builtins)[cmd];
}

/////////////////////////////////////////////////////////////////////////////

BUILTIN(source, "source")
{
	if(argv.size()<2) {
		job->reportError("source: filename argument required");
		return EX_BADUSAGE;
	}
	
	return executeFile(argv[1].c_str(), execEnv, job);
}

BUILTIN(cd, "cd")
{
	string dest;
	if(argv.size()>1)
		dest = argv[1];
	else
		dest = getenv("HOME");
	int ret = chdir(dest.c_str());
	if(ret<0) {
		string errorMessage = dest+": "+strerror(errno);
		job->reportError(errorMessage.c_str());
		return 1;
	}
	
	// Update working directory
	char buf[1024];
	getcwd(buf, 1024);
	Environment *env = &job->getState()->env;
	env->set("PWD", buf);
	
	return 0;
}

BUILTIN(exit, "exit")
{
	//shell->doExit();
	job->shellExit();
	return 0;
}

BUILTIN(get, "get")
{
	bool recursive = false;
	vector<string> files;
	
	for(unsigned ii=1; ii<argv.size(); ii++)
	{
		if(argv[ii] == "-h" || argv[ii] == "-help" || argv[ii] == "--help")
		{
			job->write("Usage: get [files]");
			return 0;
		}
		else if(argv[ii] == "-R")
		{
			// TODO: Support -R option
			recursive = true;
		}
		else if(argv[ii][0] == '-')
		{
			job->reportError("Unrecognized option");
		}
		else
		{
			files.push_back(argv[ii]);
		}
	}
	
	job->startClientWidget();
	
	// Send the list to the client for confirmation
	RPCParams getMessageParams;
		getMessageParams.set("job", job->jobid);
		StreamSender *listSender = new StreamSender(clientConnection, &getMessageParams);
	clientConnection->call("confirmget", &getMessageParams);
	
	for(unsigned ii=0; ii<files.size(); ii++)
	{
		string filename = quoteProtectString(files[ii]) + "\n";
		listSender->send(filename.length(), filename.c_str());
	}
	listSender->close();
	
	job->waitForDismissal();
	job->endClientWidget();
	
	return 0;
}

BUILTIN(put, "put")
{
	vector<string> files;
	
	for(unsigned ii=1; ii<argv.size(); ii++)
	{
		if(argv[ii] == "-h" || argv[ii] == "-help" || argv[ii] == "--help")
		{
			job->write("Usage: put [files]");
			return 0;
		}
		else if(argv[ii][0] == '-')
		{
			job->reportError("Unrecognized option");
		}
		else
		{
			files.push_back(argv[ii]);
		}
	}
	
	job->startClientWidget();
	
	RPCParams putMessageParams;
		putMessageParams.set("job", job->jobid);
		StreamSender *listSender = new StreamSender(clientConnection, &putMessageParams);
	clientConnection->call("requestput", &putMessageParams);
	
	for(unsigned ii=0; ii<files.size(); ii++)
	{
		string filename = quoteProtectString(files[ii]) + "\n";
		listSender->send(filename.length(), filename.c_str());
	}
	listSender->close();
	
	job->waitForDismissal();
	job->endClientWidget();
	
	return 0;
}

BUILTIN(localview, "localview")
{
	string file;
	
	for(unsigned ii=1; ii<argv.size(); ii++)
	{
		if(argv[ii] == "-h" || argv[ii] == "-help" || argv[ii] == "--help")
		{
			job->write("Usage: localview [file]");
			return 0;
		}
		else
		{
			file = argv[ii];
		}
	}
	
	/*int fd = open(file.c_str(), O_RDONLY);
	
	if(fd < 0) {
		char buf[512];
		snprintf(buf, 512, "Failed to open %s: %s.", file.c_str(), strerror(errno));
		job->reportError(buf);
		return 1;
	}
	
	RPCParams localViewParams;
		StreamSender *stream = new StreamSender(clientConnection, &localViewParams);
		FileSender *sender = new FileSender(clientConnection, fd, stream, job);
		
		localViewParams.set("job", job->jobid);
		localViewParams.set("file", file);
		localViewParams.set("size", (int64_t)sender->getFileSize());
	clientConnection->call("localview", &localViewParams);
	
	while(!sender->isFinished())
		job->handleEvents();
	
	delete sender;
	delete stream;
	
	close(fd);*/
	
	ShellState *state = job->getState();
	string fullPath = Path(file).resolve(Path(state->env.get("PWD"))).toString();
	
	RPCParams localViewParams;
		localViewParams.set("job", job->jobid);
		localViewParams.set("file", fullPath);
	clientConnection->call("localview", &localViewParams);
	
	return 0;
}

BUILTIN(lcd, "lcd")
{
	string path;
	
	for(unsigned ii=1; ii<argv.size(); ii++)
	{
		if(argv[ii] == "-h" || argv[ii] == "-help" || argv[ii] == "--help")
		{
			job->write("Usage: lcd [directory]");
			return 0;
		}
		else if(argv[ii][0] == '-')
		{
			string message = string("Unrecognized option: ") + argv[ii];
			job->write(message.c_str());
			return EX_BADUSAGE;
		}
		else
		{
			path = argv[ii];
		}
	}
	
	job->startClientWidget();
	RPCParams lcdMessageParams;
		lcdMessageParams.set("job", job->jobid);
		lcdMessageParams.set("path", path);
	clientConnection->call("lcd", &lcdMessageParams);
	job->endClientWidget();
	
	return 0;
}

BUILTIN(tabname, "tabname")
{
	string name;
	
	for(unsigned ii=1; ii<argv.size(); ii++)
	{
		if(argv[ii] == "-h" || argv[ii] == "-help" || argv[ii] == "--help")
		{
			job->write("Usage: tabname [text]");
			return 0;
		}
		else if(argv[ii][0] == '-')
		{
			string message = string("Unrecognized option: ") + argv[ii];
			job->write(message.c_str());
			return EX_BADUSAGE;
		}
		else
		{
			name = argv[ii];
		}
	}
	
	RPCParams tabNameParams;
		tabNameParams.set("name", name);
	clientConnection->call("tabname", &tabNameParams);
	
	return 0;
}

BUILTIN(crashtest, "crashtest")
{
	bool mainThread = false;
	
	for(unsigned ii=1; ii<argv.size(); ii++)
	{
		if(argv[ii] == "-m") {
			mainThread = true;
		}
	}
	
	if(mainThread) {
		eventManager->addEvent(new CrashTestEvent());
	} else {
		*((char*)0) = 0;
	}
	return 1;
}

